package com.hjm.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.hjm.common.core.constants.Constants;
import com.hjm.common.core.enums.ResultCode;
import com.hjm.security.exception.ServiceException;
import com.hjm.system.domain.exam.Exam;
import com.hjm.system.domain.exam.ExamQuestion;
import com.hjm.system.domain.exam.dto.ExamSaveDTO;
import com.hjm.system.domain.exam.dto.ExamQueryDTO;
import com.hjm.system.domain.exam.dto.ExamQuestionAddDTO;
import com.hjm.system.domain.exam.vo.ExamDetailVO;
import com.hjm.system.domain.exam.vo.ExamVO;
import com.hjm.system.domain.question.Question;
import com.hjm.system.domain.question.vo.QuestionVO;
import com.hjm.system.manage.ExamCacheManager;
import com.hjm.system.mapper.exam.ExamMapper;
import com.hjm.system.mapper.exam.ExamQuestionMapper;
import com.hjm.system.mapper.question.QuestionMapper;
import com.hjm.system.service.exam.ExamService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;


@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements ExamService {

    @Autowired
    ExamMapper examMapper;

    @Autowired
    QuestionMapper questionMapper;

    @Autowired
    ExamQuestionMapper examQuestionMapper;


    @Autowired
    ExamCacheManager examCacheManager;

    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {

        PageHelper.startPage(examQueryDTO.getPageNum(),
                examQueryDTO.getPageSize());

        return examMapper.selectExamList(examQueryDTO);
    }

    @Override
    public String add(ExamSaveDTO examAddDTO) {
        // 检查参数，包括 title ，时间
        checkParams(examAddDTO);

        // 将 提交的内容进行保存到 数据库
        Exam exam = new Exam();
        BeanUtils.copyProperties(examAddDTO, exam);

        int insert = examMapper.insert(exam);
        if(insert > 0) {
            return exam.getExamId().toString();
        }
        throw new ServiceException(ResultCode.ERROR);
    }

    @Override
    public int edit(ExamSaveDTO examAddDTO) {
        checkParams(examAddDTO);
        Exam exam = new Exam();
        BeanUtils.copyProperties(examAddDTO, exam);
        return examMapper.updateById(exam);
    }

    @Override
    public int check(Long examId) {
        Exam exam = getExam(examId);
        Long questionCount = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        if(questionCount > 0) {
            return Constants.EXAM_HAS_QUESTION;
        }
        if(exam.getStatus().equals(Constants.EXAM_FAILED_STATUS)){
            return Constants.EXAM_NOT_PUBLISH;
        }
        exam.setStatus(Constants.EXAM_FAILED_STATUS);
        return examMapper.updateById(exam);
    }


    @Override
    public boolean addQuestion(ExamQuestionAddDTO queryDTO) {
        // 判断 竞赛是否存在
        Exam exam = getExam(queryDTO.getExamId());

        HashSet<Long> questionIds = queryDTO.getQuestionIds();

/*      // 批量进行2次数据库操作
        for(Long questionId : questionIds) {
            // 判断题目是否存在
            Question question = questionMapper.selectById(questionId);
            if (BeanUtil.isEmpty(question)) {
                throw new ServiceException(ResultCode.EXAM_QUESTION_IS_NOT_EXISTS);
            }

            // 竞赛，题目都存在，现在进行数据库插入操作
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(num++);

            examQuestionMapper.insert(examQuestion);
        }*/

        // 这里将上面 多次数据库查询 更改为使用 一条多量查询
        List<Question> questions = questionMapper.selectBatchIds(questionIds);
        if(questions == null || questions.size() < questionIds.size()){
            throw new ServiceException(ResultCode.EXAM_QUESTION_IS_NOT_EXISTS);
        }

        return saveExamQuestion(exam, questionIds);

    }



    @Override
    public ExamDetailVO detail(Long examId) {
        ExamDetailVO examDetailVO = new ExamDetailVO();
        // 获取 exam 的信息
        Exam exam = getExam(examId);
        BeanUtil.copyProperties(exam, examDetailVO);

        // 根据 examId 从竞赛题目的表中取出 题目的 questionId
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));
        if(CollectionUtil.isEmpty(examQuestionList)){
            return  examDetailVO;
        }
        // 类型转换
        List<Long> questionIdList = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();

        // 根据 questionIdlist 从数据库中进行 题目 的 查询
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty)
                .in(Question::getQuestionId, questionIdList));

        List<QuestionVO> questionVOList = BeanUtil.copyToList(questionList, QuestionVO.class);

        examDetailVO.setExamQuestionList(questionVOList);
        return examDetailVO;
    }

    @Override
    public boolean deleteQuestion(Long examId, Long questionId) {
        // 查竞赛
        Exam exam = getExam(examId);

        // 删除 竞赛中 题目
        int delete = examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().
                eq(ExamQuestion::getExamId, examId).
                eq(ExamQuestion::getQuestionId, questionId));

        // todo 题目不存在怎么办
        if(delete < 1){
            throw new ServiceException(ResultCode.EXAM_QUESTION_IS_NOT_EXISTS);
        }

        // 还要对竞赛题目顺序 进行修改
        List<ExamQuestion> examQuestions = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>().
                eq(ExamQuestion::getExamId, examId).
                select(ExamQuestion::getExamQuestionId).
                orderByAsc(ExamQuestion::getQuestionOrder));
        if(CollectionUtil.isNotEmpty(examQuestions) && delete > 0){
            int num=1;
            for (ExamQuestion examQuestion : examQuestions) {
                examQuestion.setQuestionOrder(num++);
            }
            return  updateBatchById(examQuestions);
        }
        return true;

    }

    @Override
    public int delete(Long examId) {
        Exam exam = getExam(examId);
        LocalDateTime startTime = exam.getStartTime();
        if (startTime.isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_BEGIN);
        }
        //删除竞赛-题目关系
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        // 删除竞赛 和 缓存

        examCacheManager.deleteCache(examId);
        return examMapper.deleteById(examId);
    }


    @Override
    public int publish(Long examId) {
        Exam exam = getExam(examId);
        if(exam.getEndTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_IS_END);
        }

        // 检查 竞赛发布前 是否含有 题目
        Long examQuestions = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        if(examQuestions <= 0){
            throw new ServiceException(ResultCode.EXAM_NO_INCLUDE_QUESTION);
        }

        exam.setStatus(Constants.EXAM_SUCCESS_STATUS);

        // 将信息存入 redis 中
        ExamSaveDTO examSaveDTO = new ExamSaveDTO();
        BeanUtil.copyProperties(exam, examSaveDTO);
        examCacheManager.addCache(examSaveDTO);
        return examMapper.updateById(exam);
    }

    @Override
    public int cancelPublish(Long examId) {
        Exam exam = getExam(examId);
        exam.setStatus(Constants.EXAM_FAILED_STATUS);

        examCacheManager.deleteCache(examId);

        return examMapper.updateById(exam);
    }




    // 根据 examId 获取 exam 的信息
    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (BeanUtil.isEmpty(exam)) {
            throw new ServiceException(ResultCode.EXAM_IS_NOT_EXISTS);
        }
        return exam;
    }

    // 存储竞赛 --- 题目
    private boolean saveExamQuestion(Exam exam, HashSet<Long> questionIds ) {
        // 排序用
        List<ExamQuestion> examQuestions = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, exam.getExamId()));

        int num=examQuestions.size()+1;
        // 一次进行多条数据的存储，并没有现成的方法，不过在mybatis-plus的拓展包中ServiceImpl 中已经实现了
        // 所以，我们可以继承 来使用其中的方法  ServiceImpl<Mapper, 实体类>
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        for (Long questionId : questionIds) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(num++);
            examQuestionList.add(examQuestion);
        }
        return saveBatch(examQuestionList);
    }


    // 判断 开始时间是否小于 结束时间，开始时间是否 大于 当前时间
    private  void checkParams(ExamSaveDTO examAddDTO) {

        List<Exam> examlsit = examMapper.selectList(new LambdaQueryWrapper<Exam>().select().
                eq(Exam::getTitle, examAddDTO.getTitle()).
                ne(examAddDTO.getExamId() != null,Exam::getExamId, examAddDTO.getExamId()));

        if(CollectionUtil.isNotEmpty(examlsit)){
            throw new ServiceException(ResultCode.EXAM_IS_EXISTS);
        }
        if( examAddDTO.getStartTime().isBefore(LocalDateTime.now())){
            // 当前时间 < 开始时间
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_NOW_TIME);
        }
        if( examAddDTO.getStartTime().isAfter(examAddDTO.getEndTime())){
            // 结束时间 < 开始时间
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }






}
